/**
* Copyright 2012 nabla
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
*/
package com.nabla.dc.server.database;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletContext;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.nabla.dc.server.IRoles;
import com.nabla.wapp.report.server.ReportManager;
import com.nabla.wapp.server.auth.IRole;
import com.nabla.wapp.server.auth.UserManager;
import com.nabla.wapp.server.database.ConnectionTransactionGuard;
import com.nabla.wapp.server.database.Database;
import com.nabla.wapp.server.database.MySqlStatementList;
import com.nabla.wapp.server.general.Util;
import com.nabla.wapp.shared.dispatch.DispatchException;
import com.nabla.wapp.shared.dispatch.InternalErrorException;


@Singleton
public class MyReadWriteDatabase extends Database {

	private static final Log	log = LogFactory.getLog(MyReadWriteDatabase.class);

	@Inject
	public MyReadWriteDatabase(final ServletContext serverContext, final ReportManager reportManager) throws SQLException, DispatchException, IOException {
		super("dcrw", serverContext);

		if ("1".equals(serverContext.getInitParameter(PRODUCTION_MODE))) {
			// PRODUCTION MODE
			if ("1".equals(serverContext.getInitParameter(SLAVE_MODE))) {
				// SLAVE SERVER
			} else {
				// MASTER SERVER
				final Connection conn = getConnection();
				try {
					final ConnectionTransactionGuard guard = new ConnectionTransactionGuard(conn);
					try {
						final Integer currentVersion = Database.getVersion(conn);
						log.debug("current version = " + currentVersion);
						final File databaseSchemaFolder = new File(serverContext.getRealPath("/WEB-INF/classes/" + MyReadWriteDatabase.class.getPackage().getName().replace('.', '/') + "/"));
						Integer organisationId;
						if (currentVersion == 0) {
							initializeDatabase(conn, databaseSchemaFolder);
							organisationId = getDefaultOrganisationId(conn);
							initializeUsers(conn, organisationId, serverContext);
						} else {
							updateDatabase(currentVersion, conn, databaseSchemaFolder);
							organisationId = getDefaultOrganisationId(conn);
						}
						initializeReports(conn, organisationId, serverContext, reportManager);
						guard.setSuccess();
					} finally {
						guard.close();
					}
				} finally {
					conn.close();
				}
			}
		} else {
			// DEVELOPMENT MODE

		}
	}

	private Integer getDefaultOrganisationId(final Connection conn) throws SQLException {
		final PreparedStatement stmt = MySqlResource.prepare(conn, "com.nabla.dc.server.MyReadWriteDatabase.getDefaultOrganisationId");
		try {
			final ResultSet rs = stmt.executeQuery();
			try {
				rs.next();
				return rs.getInt(1);
			} finally {
				rs.close();
			}
		} finally {
			stmt.close();
		}
	}

	private void initializeDatabase(final Connection conn, final File databaseSchemaFolder) throws SQLException, IOException {
		if (log.isDebugEnabled())
			log.debug("initializing database");
		final MySqlStatementList statements = new MySqlStatementList();
		statements.addAll(databaseSchemaFolder.listFiles((FileFilter)new SuffixFileFilter("_current.sql")));
		final Statement stmt = conn.createStatement();
   		try {
   			for (String statement : statements) {
   				if (log.isDebugEnabled())
   					log.debug("statement: " + statement);
   				stmt.executeUpdate(statement);
   			}
   		} finally {
   			stmt.close();
   		}
	}

	private void updateDatabase(final Integer currentVersion, final Connection conn, final File databaseSchemaFolder) throws SQLException, IOException {
		final MySqlStatementList statements = new MySqlStatementList();
		Integer version = currentVersion + 1;
		while (statements.addAll(databaseSchemaFolder.listFiles((FileFilter)new SuffixFileFilter(String.format("_%d.sql", version)))))
			++version;
		if (!statements.isEmpty()) {
			if (log.isDebugEnabled())
				log.debug("updating database up to version " + version);
			final Statement stmt = conn.createStatement();
    		try {
    			for (String statement : statements) {
       				if (log.isDebugEnabled())
       					log.debug("statement: " + statement);
    				stmt.executeUpdate(statement);
    			}
    		} finally {
    			stmt.close();
    		}
		}
	}

	private void initializeUsers(final Connection conn, final Integer organisationId, final ServletContext serverContext) throws SQLException {
		new UserManager(conn).initializeDatabase(organisationId, new UserManager.IRoleListProvider() {
			@Override
			public Map<String, String[]> get() {
				final Map<String, String[]> rslt = new HashMap<String, String[]>();
				for (final Field field : IRoles.class.getFields()) {
					if (!field.getType().getCanonicalName().equals(String.class.getName()))
						continue;
					try {
						final String roleName = (String)field.get(null);	// assume static
						final IRole definition = field.getAnnotation(IRole.class);
						if (definition == null) {
							// privilege
							rslt.put(roleName, null);
						} else {
							// role
							rslt.put(roleName, definition.value());
						}
					} catch (final IllegalArgumentException e) {
						if (log.isDebugEnabled())
							log.debug("invalid field '" + field.getName() + "' as a role", e);
					} catch (final IllegalAccessException e) {
						if (log.isDebugEnabled())
							log.debug("invalid field '" + field.getName() + "' as a role", e);
					}
				}
				return rslt;
			}
		}, serverContext.getInitParameter("root_password"));
	}

	private void initializeReports(final Connection conn, final Integer organisationId, final ServletContext serverContext, final ReportManager reportManager) throws SQLException, DispatchException {
		if (log.isDebugEnabled())
			log.debug("loading internal report table");
		MySqlResource.execute(conn, "com.nabla.dc.server.MyReadWriteDatabase.resetExternalReports");
		final File reportFolder = new File(serverContext.getRealPath(ReportManager.INTERNAL_REPORT_FOLDER));
		for (File folder : reportFolder.listFiles((FileFilter)DirectoryFileFilter.INSTANCE))
			loadInternalReport(conn, organisationId, folder, reportManager);
	}

	private boolean loadInternalReport(final Connection conn, final Integer organisationId, final File folder, final ReportManager reportManager) throws SQLException, DispatchException {
		final File[] files = folder.listFiles((FileFilter)new SuffixFileFilter(ReportManager.REPORT_FILE_EXTENSION));
		if (files.length != 1) {
        	if (log.isDebugEnabled())
        		log.debug("skip folder '" + folder.getName() + "': no report file found");
        	return false;
        }
        final File reportFile = files[0];
        InputStream in;
        InputStream design;
		try {
			in = new FileInputStream(reportFile);
			design = new FileInputStream(reportFile);
		} catch (FileNotFoundException e) {
			throw new InternalErrorException(Util.formatInternalErrorDescription(e));
		}
		int reportId = reportManager.addReport(conn, organisationId, FilenameUtils.getBaseName(reportFile.getName()), folder.getName(), design, in);
		for (File file : folder.listFiles((FileFilter)new SuffixFileFilter(ReportManager.RESOURCE_FILE_EXTENSIONS))) {
        	try {
				reportManager.loadReportResource(conn, reportId, file.getName(), new FileInputStream(file));
			} catch (FileNotFoundException e) {
				throw new InternalErrorException(Util.formatInternalErrorDescription(e));
			}
		}
		for (File file : folder.listFiles((FileFilter)new SuffixFileFilter(ReportManager.PROPERTIES_FILE_EXTENSION))) {
			try {
				reportManager.loadLocaleReportName(conn, reportId, file.getName(), new FileInputStream(file));
			} catch (FileNotFoundException e) {
				throw new InternalErrorException(Util.formatInternalErrorDescription(e));
			}
		}
		return true;
	}
}
